import { RouterContext } from '@koa/router'
import axios from 'axios'
import { Controller, Middlewares, Post } from 'koa-decorator-helpers'
import { AccountType } from '~/constants/AccountType'
import { MsgDataType } from '~/constants/MsgDataType'
import { ProxyMode } from '~/constants/ProxyMode'
import { TestAccounts } from '~/constants/TestAccounts'
import { TokenType } from '~/constants/TokenType'
import { getAccount } from '~/data/account'
import { getAccountProxies } from '~/data/proxy'
import { getComponentAccessToken } from '~/data/token'
import { TokenManager } from '~/data/token-manager'
import { Account, AccountProxy, Token } from '~/db'
import { componentEncryptedBody } from '~/libs/validator'
import * as api from '~/libs/wx/api'
import { decrpytMsg, encryptMsg, getSha1Signature } from '~/libs/wx/crypt'
import { getMsgSignature } from '~/libs/wx/msg'
import { buildXml, parseXml } from '~/libs/wx/xml'
import xmlBody from '~/middlewares/xml-body'

/**
 * 负责处理所有回调和通知的控制器
 */
@Controller({ prefix: '/callback' })
@Middlewares([xmlBody()])
export class Callback {
    /**
     * 第三方平台通知回调
     * @param ctx
     */
    @Post('/component')
    async component(ctx: RouterContext) {
        const success = () => 'success'
        const fail = () => {
            ctx.status = 400
        }

        //请求体校验
        if (!componentEncryptedBody(ctx.request.body)) {
            return fail()
        }

        const { appid, encrypt } = ctx.request.body

        //读取第三方平台的数据
        const account = await getAccount(appid)
        if (!account) {
            return fail()
        }

        //解密消息
        const decrypted = decrpytMsg(encrypt, account.encoding_aes_key!)
        const msg = parseXml<Weixin.Component.IncomingMessage>(decrypted.decrypted)

        //进行消息处理
        switch (msg.infotype) {
            case 'component_verify_ticket':
                //收到验证票据的推送
                await this.onComponentVerifyTicket(account, msg)
                break
            case 'authorized':
            case 'updateauthorized':
                await this.onAuthorized(account, msg)
                break
            case 'unauthorized':
                await this.onUnauthorized(account, msg)
                break
        }

        return success()
    }

    /**
     * 当收到推送的第三方平台校验凭据时进行的操作
     */
    protected async onComponentVerifyTicket(account: Account, msg: Weixin.Component.VerifyTicketMessage) {
        //将票据写入数据库
        await Token.upsert({
            app_id: account.app_id,
            component_app_id: '',
            type: TokenType.ComponentVerifyTicket,
            token: msg.componentverifyticket,
            expire_time: new Date(Date.now() + 12 * 3600000),
        })
    }

    /**
     * 公众号/小程序授权时进行的操作
     * @param account
     * @param msg
     */
    protected async onAuthorized(component: Account, msg: Weixin.Component.AuthorizeMessage) {
        //查询授权账号的信息
        const component_access_token = await getComponentAccessToken(component.app_id)
        if (!component_access_token) {
            console.error('无法获取 component_access_token')
            return
        }

        const ret = await api.component.queryAuth(component.app_id, component_access_token, msg.authorizationcode)

        //写入授权数据库
        const account = await Account.findOne({
            where: {
                app_id: ret.authorization_info.authorizer_appid,
                component_app_id: component.app_id,
            },
            paranoid: false,
        })
        if (account) {
            account.delete_time = null
            account.extra = {
                ...account.extra,
                refresh_token: ret.authorization_info.authorizer_refresh_token,
                func_info: ret.authorization_info.func_info.map((item) => item.funcscope_category.id),
            }
            await account.save()
        } else {
            await Account.create({
                app_id: ret.authorization_info.authorizer_appid,
                component_app_id: component.app_id,
                type: AccountType.Mp,
                extra: {
                    refresh_token: ret.authorization_info.authorizer_refresh_token,
                    func_info: ret.authorization_info.func_info.map((item) => item.funcscope_category.id),
                },
            })
        }

        await TokenManager.set({
            app_id: ret.authorization_info.authorizer_appid,
            component_app_id: component.app_id,
            type: TokenType.AccessToken,
            token: ret.authorization_info.authorizer_access_token,
            expire_time: new Date(Date.now() + ret.authorization_info.expires_in * 1000),
        })
    }

    protected async onUnauthorized(component: Account, msg: Weixin.Component.UnauthorizeMessage) {
        await Account.destroy({
            where: {
                app_id: msg.authorizerappid,
                component_app_id: component.app_id,
            },
        })
        await Token.update(
            {
                expire_time: new Date(),
            },
            {
                where: {
                    app_id: msg.authorizerappid,
                    component_app_id: component.app_id,
                },
            },
        )
        await TokenManager.clean(msg.authorizerappid, component.app_id)
    }

    /**
     * 公众号/小程序直接托管回调
     * @param ctx
     */
    @Post('/account/:component_app_id/:app_id')
    async account(ctx: RouterContext) {
        const unparsed = Symbol.for('unparsed')
        console.log(ctx.request.body[unparsed])
        ctx.status = 200

        const { component_app_id, app_id } = ctx.params

        // if (!TestAccounts.some((t) => t.app_id === app_id)) return ''

        //读取第三方平台的数据
        const account = await getAccount(component_app_id)
        if (!account) {
            return 'success'
        }

        //解密消息
        const decrypted = decrpytMsg(ctx.request.body.encrypt, account.encoding_aes_key!)
        const msg = parseXml<Weixin.Mp.BaseIncomingMessage>(decrypted.decrypted)

        console.log(msg)

        //消息转发
        this.proxyAccountMsg(app_id, msg.tousername, decrypted.decrypted)

        switch (msg.msgtype) {
            case 'text':
                if (msg.content === 'TESTCOMPONENT_MSG_TYPE_TEXT') {
                }
                break
            case 'event':
                if (msg.event === 'SCAN') {
                }
                break
        }

        return 'success'
    }

    /**
     * 执行消息转发
     */
    protected async proxyAccountMsg(app_id: string, to_user_name: string, msg: string) {
        const proxies = await getAccountProxies(app_id)
        if (proxies.length === 0) return
        proxies.forEach((proxy) =>
            this.proxyAccountSingleMsg(proxy, to_user_name, msg).catch((err) => {
                console.error(err)
            }),
        )
    }

    /**
     * 执行单个消息转发
     * @param app_id
     * @param msg
     * @returns
     */
    protected async proxyAccountSingleMsg(proxy: AccountProxy, to_user_name: string, msg: string) {
        const timestamp = Math.floor(Date.now() / 1000).toString()
        const nonce = Math.random().toString().substring(2)
        const signature = getSha1Signature({
            token: proxy.msg_token ?? '',
            timestamp,
            nonce,
        })

        if (proxy.mode === ProxyMode.Plain || !proxy.encoding_aes_key) {
            //执行明文转发
            const data = proxy.data_type === MsgDataType.JSON ? parseXml(msg, false) : msg
            const resp = await axios.request({
                method: 'POST',
                url: proxy.proxy_url,
                params: {
                    signature,
                    timestamp,
                    nonce,
                },
                data,
            })

            console.log('消息转发 RAW', proxy.proxy_url, {
                signature,
                timestamp,
                nonce,
            })
            console.log(resp.data)

            return
        }

        //加密消息体
        const encrypted = encryptMsg(msg, proxy.encoding_aes_key, proxy.app_id)
        let msg_encrypted = {
            ToUserName: to_user_name,
            Encrypt: encrypted,
        }

        if (proxy.mode === ProxyMode.Both) {
            msg_encrypted = Object.assign(parseXml(msg, false), msg_encrypted)
        }

        //计算消息签名
        const msg_signature = getSha1Signature({
            token: proxy.msg_token ?? '',
            timestamp,
            nonce,
            encrypt: encrypted,
        })

        const data = proxy.data_type === MsgDataType.JSON ? msg_encrypted : buildXml(msg_encrypted)

        //执行转发
        const resp = await axios.request({
            timeout: 3000,
            method: 'POST',
            url: proxy.proxy_url,
            params: {
                signature,
                timestamp,
                nonce,
                msg_signature,
                encrypt_type: 'aes',
            },
            data,
        })

        console.log('消息转发', proxy.proxy_url, {
            signature,
            timestamp,
            nonce,
            msg_signature,
            encrypt_type: 'aes',
        })
        console.log(resp.data)
    }
}
